package mybatis.executor;

import mybatis.builder.ParameterMappingTokenHandler;
import mybatis.exception.ExecutorException;
import mybatis.mapping.BoundSql;
import mybatis.mapping.MappedStatement;
import mybatis.parsing.GenericTokenParser;
import mybatis.session.Configuration;
import mybatis.transaction.Transaction;
import utils.LogUtils;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/**
 * SQL查询执行器通用实现基类（用于扩展简单查询、存储过程查询和游标查询等子类）
 *
 * @author richie696
 * @version 1.0
 * @since 2020/11/07
 */
public abstract class BaseExecutor implements Executor {

    protected Configuration configuration;
    protected Transaction transaction;
    private boolean closed;

    protected BaseExecutor(Configuration configuration, Transaction transaction) {
        this.transaction = transaction;
        this.configuration = configuration;
        this.closed = false;
    }

    @Override
    public Transaction getTransaction() {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        return transaction;
    }

    /**
     * 执行数据库更新操作的方法
     *
     * @param mappedStatement SQL映射关系对象
     * @param parameter       更新参数
     * @return 返回受影响的数据库行数
     */
    @Override
    public int update(MappedStatement mappedStatement, Object parameter) {
        if (closed) {
            throw new ExecutorException("执行器对象已关闭。");
        }
        // 获取SQL模板
        String sqlText = mappedStatement.getSql();
        // 转换执行DB查询用的SQL对象（非xml内写的语句模板）
        BoundSql boundSql = getBoundSql(sqlText);
        // 执行更新

        try {
            return doUpdate(mappedStatement, boundSql, parameter);
        } catch (SQLException ex) {
            throw new ExecutorException(ex);
        }
    }

    /**
     * 执行数据库查询的方法
     *
     * @param mappedStatement SQL映射关系对象
     * @param parameters      查询参数
     * @param <E>             查询结果泛型对象
     * @return 返回符合查询条件的结果对象
     * @throws SQLException 当查询过程中产生数据库相关的错误时抛出该异常
     */
    @Override
    public <E> List<E> query(MappedStatement mappedStatement, Object... parameters) throws SQLException {
        // 获取SQL模板
        String sqlText = mappedStatement.getSql();
        // 转换执行DB查询用的SQL对象（非xml内写的语句模板）
        BoundSql boundSql = getBoundSql(sqlText);
        // 执行查询
        return doQuery(mappedStatement, boundSql, parameters);
    }

    @Override
    public void close(boolean forceRollback) {
        try {
            try {
                rollback(forceRollback);
            } finally {
                if (transaction != null) {
                    transaction.close();
                }
            }
        } catch (SQLException e) {
            LogUtils.warn("关闭数据库事务时出现异常，原因：" + e);
        } finally {
            transaction = null;
            closed = true;
        }
    }

    @Override
    public boolean isClosed() {
        return closed;
    }

    @Override
    public void commit(boolean force) throws SQLException {
        if (closed) {
            throw new ExecutorException("无法提交，事务已经关闭。");
        }
        if (force) {
            transaction.commit();
        }
    }

    @Override
    public void rollback(boolean force) throws SQLException {
        if (closed) {
            throw new ExecutorException("无法回滚，事务已经关闭。");
        }
        if (force) {
            transaction.rollback();
        }
    }

    /**
     * 执行具体更新操作的方法
     *
     * @param ms        SQL映射关系对象
     * @param boundSql  SQL语句对象
     * @param parameter 更新参数
     * @return 返回受影响的行数
     * @throws SQLException 当查询过程中产生数据库相关的错误时抛出该异常
     */
    protected abstract int doUpdate(MappedStatement ms, BoundSql boundSql, Object parameter) throws SQLException;

    /**
     * 执行具体查询操作的方法
     *
     * @param ms         SQL映射关系对象
     * @param boundSql   SQL语句对象
     * @param parameters 查询参数
     * @param <E>        查询结果泛型对象
     * @return 返回符合查询条件的结果对象
     * @throws SQLException 当查询过程中产生数据库相关的错误时抛出该异常
     */
    protected abstract <E> List<E> doQuery(MappedStatement ms, BoundSql boundSql, Object... parameters)
            throws SQLException;

    /**
     * 转换执行DB查询用的SQL对象的方法
     *
     * @param sqlText 待转换的SQL模板
     * @return 返回SQL语句
     */
    private BoundSql getBoundSql(String sqlText) {
        ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser tokenParser = new GenericTokenParser("#{", "}", tokenHandler);
        String dbSql = tokenParser.parse(sqlText);
        return new BoundSql(dbSql, tokenHandler.getParameterMappings());
    }

    protected Connection getConnection() throws SQLException {
        return transaction.getConnection();
    }
}
